/*
 * Copyright (c) 2014 Andy Huang <andyspider@126.com>
 *
 * This file is part of Camkit.
 *
 * Camkit is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Camkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Camkit; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "ffmpeg_common.h"

#include <libavutil/mathematics.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>

#include "camkit/decode.h"

#define CALC_FFMPEG_VERSION(a,b,c) ( a<<16 | b<<8 | c )

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif

#if defined(__APPLE__)
#define AV_NOPTS_VALUE_ ((int64_t)0x8000000000000000LL)
#else
#define AV_NOPTS_VALUE_ ((int64_t)AV_NOPTS_VALUE)
#endif

#ifndef AVERROR_EOF
#define AVERROR_EOF (-MKTAG( 'E','O','F',' '))
#endif

#if LIBAVCODEC_BUILD >= CALC_FFMPEG_VERSION(54,25,0)
#  define CV_CODEC_ID AVCodecID
#  define CV_CODEC(name) AV_##name
#else
#  define CV_CODEC_ID CodecID
#  define CV_CODEC(name) name
#endif

#define CV_WARN printf

//#ifdef NDEBUG
//#define CV_WARN(message)
//#else
//#define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__)
//#endif

typedef enum {
	false, 
	true
} bool;

typedef struct _Image_FFMPEG
{
	unsigned char* data;
	int step;
	int width;
	int height;
	int cn;
}Image_FFMPEG;

struct dec_handle
{
	AVFormatContext * ic;
	AVCodec         * avcodec;
	int               video_stream;
	AVStream        * video_st;
	AVFrame         * picture;
	AVFrame           yuv_frame;
	int64_t           frame_pts;

	AVPacket          packet;
	Image_FFMPEG      frame;
	struct SwsContext *img_convert_ctx;

	int64_t frame_number, first_frame_number;

	double eps_zero;

	struct dec_param params;
};

void init(struct dec_handle* handle);
void _close(struct dec_handle *handle);

double getProperty(struct dec_handle *handle, int value);
bool setProperty(struct dec_handle *handle, int p, double value);
bool grabFrame(struct dec_handle *handle);
bool retrieveFrame(struct dec_handle *handle, int no, unsigned char** data, int* step, int* width, int* height, int* cn);

void    seek(struct dec_handle *handle, int64_t frame_number);
//void    seek(struct dec_handle *handle, double sec);
bool    slowSeek(struct dec_handle *handle, int framenumber );

int64_t get_total_frames(struct dec_handle *handle);
double  get_duration_sec(struct dec_handle *handle);
double  get_fps(struct dec_handle *handle);
int     get_bitrate(struct dec_handle *handle);

double  r2d(struct dec_handle *handle, AVRational r);
int64_t dts_to_frame_number(struct dec_handle *handle, int64_t dts);
double  dts_to_sec(struct dec_handle *handle, int64_t dts);

static int get_number_of_cpus(void)
{
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(52, 111, 0)
    return 1;
#elif defined WIN32 || defined _WIN32
    SYSTEM_INFO sysinfo;
    GetSystemInfo( &sysinfo );

    return (int)sysinfo.dwNumberOfProcessors;
#elif defined __linux__
    return (int)sysconf( _SC_NPROCESSORS_ONLN );
#elif defined __APPLE__
    int numCPU=0;
    int mib[4];
    size_t len = sizeof(numCPU);

    // set the mib for hw.ncpu
    mib[0] = CTL_HW;
    mib[1] = HW_AVAILCPU;  // alternatively, try HW_NCPU;

    // get the number of CPUs from the system
    sysctl(mib, 2, &numCPU, &len, NULL, 0);

    if( numCPU < 1 )
    {
        mib[1] = HW_NCPU;
        sysctl( mib, 2, &numCPU, &len, NULL, 0 );

        if( numCPU < 1 )
            numCPU = 1;
    }

    return (int)numCPU;
#else
    return 1;
#endif
}

struct dec_handle *decode_open(struct dec_param param)
{
    unsigned i;
    bool valid = false;

	struct dec_handle *handle = malloc(sizeof(struct dec_handle));
	if (!handle)
	{
		printf("--- malloc dec handle failed\n");
		return NULL;
	}

	CLEAR(*handle);

	_close(handle);

	
#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0)
    int err = avformat_open_input(&(handle->ic), param.filename, NULL, NULL);
#else
    int err = av_open_input_file(&(handle->ic), param.filename, NULL, 0, NULL);
#endif

    if (err < 0)
    {
        CV_WARN("Error opening file %s err:%d", param.filename, err);
        goto exit_func;
    }
    err =
#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 6, 0)
    avformat_find_stream_info(handle->ic, NULL);
#else
    av_find_stream_info(handle->ic);
#endif
    if (err < 0)
    {
        CV_WARN("Could not find codec parameters");
        goto exit_func;
    }
    for(i = 0; i < handle->ic->nb_streams; i++)
    {
#if LIBAVFORMAT_BUILD > 4628
        AVCodecContext *enc = handle->ic->streams[i]->codec;
#else
        AVCodecContext *enc = &handle->ic->streams[i]->codec;
#endif

//#ifdef FF_API_THREAD_INIT
//        avcodec_thread_init(enc, get_number_of_cpus());
//#else
        enc->thread_count = get_number_of_cpus();
//#endif

#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0)
#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
#endif

        if( AVMEDIA_TYPE_VIDEO == enc->codec_type && handle->video_stream < 0)
        {
            // backup encoder' width/height
            int enc_width = enc->width;
            int enc_height = enc->height;

            AVCodec *codec = avcodec_find_decoder(enc->codec_id);
            if (!codec ||
#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0)
                avcodec_open2(enc, codec, NULL)
#else
                avcodec_open(enc, codec)
#endif
                < 0)
                goto exit_func;

            // checking width/height (since decoder can sometimes alter it, eg. vp6f)
            if (enc_width && (enc->width != enc_width)) { enc->width = enc_width; }
            if (enc_height && (enc->height != enc_height)) { enc->height = enc_height; }

            handle->video_stream = i;
            handle->video_st = handle->ic->streams[i];
            handle->picture = avcodec_alloc_frame();

            /*
            //此处不要申请解码输出缓存结构和内存
            handle->rgb_frame.data[0] = (uint8_t*)malloc(
                    avpicture_get_size( PIX_FMT_BGR24,
                                        enc->width, enc->height ));
            avpicture_fill( (AVPicture*)&handle->rgb_frame, handle->rgb_frame.data[0],
                            PIX_FMT_BGR24, enc->width, enc->height );

             handle->frame.width = enc->width;
             handle->frame.height = enc->height;
             handle->frame.cn = 3;
             handle->frame.step = handle->rgb_frame.linesize[0];
             handle->frame.data = handle->rgb_frame.data[0];
            */
            break;
        }
    }

    if(handle->video_stream >= 0) valid = true;

exit_func:

    if( !valid )
	{
        _close(handle);
		return NULL;
	}
	else{
		return handle;
	}
}	

void decode_close(struct dec_handle *handle)
{
	_close(handle);
	free(handle);
	printf("+++ Decode Closed\n");
}

int decode_do(struct dec_handle *handle, void **pobuf, int *polen)
{	
	int step, width, height, cn;
    if(!grabFrame(handle))
	{
		*polen = 0;
        return 0;
	}

	if (!retrieveFrame(handle, 0, pobuf, &step, &width, &height, &cn))
	{
		*polen = 0;
        return 0;
	}

	*polen = width*height*3/2;
	
	printf("decode_do out width:%d height:%d len:%d\n", width, height, *polen);

	return 0;
}

////



void init(struct dec_handle *handle)
{
	handle->ic = 0;
    handle->video_stream = -1;
    handle->video_st = 0;
    handle->picture = 0;
    handle->frame_pts = AV_NOPTS_VALUE_;
    handle->first_frame_number = -1;
    memset( &handle->yuv_frame, 0, sizeof(handle->yuv_frame) );
    memset( &handle->frame, 0, sizeof(handle->frame) );
    handle->params.filename = 0;
    memset(&handle->packet, 0, sizeof(handle->packet));
    av_init_packet(&handle->packet);
    handle->img_convert_ctx = 0;

    handle->avcodec = 0;
    handle->frame_number = 0;
    handle->eps_zero = 0.000025;

	av_register_all();

	avcodec_register_all();

}


void _close(struct dec_handle *handle)
{
    if( handle->img_convert_ctx )
    {
        sws_freeContext(handle->img_convert_ctx);
        handle->img_convert_ctx = 0;
    }

    if( handle->picture )
        av_free(handle->picture);

    if( handle->video_st )
    {
#if LIBAVFORMAT_BUILD > 4628
        avcodec_close( handle->video_st->codec );

#else
        avcodec_close( &(handle->video_st->codec) );

#endif
        handle->video_st = NULL;
    }

    if( handle->ic )
    {
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 24, 2)
        av_close_input_file(handle->ic);
#else
        avformat_close_input(&handle->ic);
#endif

        handle->ic = NULL;
    }

    if( handle->yuv_frame.data[0] )
    {
        free( handle->yuv_frame.data[0] );
        handle->yuv_frame.data[0] = 0;
    }

    // free last packet if exist
    if (handle->packet.data) {
        av_free_packet (&handle->packet);
        handle->packet.data = NULL;
    }

    init(handle);
}

bool grabFrame(struct dec_handle *handle)
{
    bool valid = false;
    int got_picture;

    int count_errs = 0;
    //const int max_number_of_attempts = 1 << 16;
    const int max_number_of_attempts = 1 << 8; //2014.06.16 yao  尝试次数过多播放AVI结束时感觉像是假死

    if( !handle->ic || !handle->video_st )  return false;

    if( handle->ic->streams[handle->video_stream]->nb_frames > 0 &&
        handle->frame_number > handle->ic->streams[handle->video_stream]->nb_frames )
        return false;

    av_free_packet (&handle->packet);

    handle->frame_pts = AV_NOPTS_VALUE_;

    // get the next frame
    while (!valid)
    {
        int ret = av_read_frame(handle->ic, &handle->packet);
        if (ret == AVERROR(EAGAIN)) continue;

        /* else if (ret < 0) break; */

        if( handle->packet.stream_index != handle->video_stream )
        {
            av_free_packet (&handle->packet);
            count_errs++;
            if (count_errs > max_number_of_attempts)
                break;
            continue;
        }

        // Decode video frame
        #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 2, 0)
            avcodec_decode_video2(handle->video_st->codec, handle->picture, &got_picture, &handle->packet);
        #elif LIBAVFORMAT_BUILD > 4628
                avcodec_decode_video(handle->video_st->codec,
                                     handle->picture, &got_picture,
                                     handle->packet.data, handle->packet.size);
        #else
                avcodec_decode_video(&handle->video_st->codec,
                                     handle->picture, &got_picture,
                                     handle->packet.data, handle->packet.size);
        #endif

        // Did we get a video frame?
        if(got_picture)
        {
            //handle->frame_pts = handle->picture->best_effort_timestamp;
            if( handle->frame_pts == AV_NOPTS_VALUE_ )
                handle->frame_pts = handle->packet.pts != AV_NOPTS_VALUE_ && handle->packet.pts != 0 ? handle->packet.pts : handle->packet.dts;
            handle->frame_number++;
            valid = true;
        }
        else
        {
            count_errs++;
            if (count_errs > max_number_of_attempts)
                break;
        }

        av_free_packet (&handle->packet);
    }

    if( valid && handle->first_frame_number < 0 )
        handle->first_frame_number = dts_to_frame_number(handle, handle->frame_pts);

    // return if we have a new handle->picture or not
    return valid;
}


bool retrieveFrame(struct dec_handle *handle, int no, unsigned char** data, int* step, int* width, int* height, int* cn)
{
    if( !handle->video_st || !handle->picture->data[0] )
        return false;

    /*
    avpicture_fill((AVPicture*)&handle->rgb_frame, handle->rgb_frame.data[0], PIX_FMT_RGB24,
                   handle->video_st->codec->width, handle->video_st->codec->height);*/

	if( handle->img_convert_ctx == NULL 
		|| (handle->yuv_frame.data[0] == NULL 
			&& (handle->frame.width != handle->video_st->codec->width 
				|| handle->frame.height != handle->video_st->codec->height)))
    {
        if( handle->yuv_frame.data[0] )
        {
            free( handle->yuv_frame.data[0] );
            handle->yuv_frame.data[0] = 0;
        }

        if (handle->frame.width == 0)
        {
             handle->frame.width = handle->video_st->codec->width;
             handle->frame.height = handle->video_st->codec->height;
        }

        handle->yuv_frame.data[0] = (uint8_t*)malloc(
                    avpicture_get_size( PIX_FMT_YUV420P,
                                         handle->frame.width,  handle->frame.height ));

        avpicture_fill( (AVPicture*)&handle->yuv_frame, handle->yuv_frame.data[0],
                        PIX_FMT_YUV420P,  handle->frame.width,  handle->frame.height );


         handle->frame.cn = 3;
         handle->frame.step = handle->yuv_frame.linesize[0];
         handle->frame.data = handle->yuv_frame.data[0];

        if( handle->img_convert_ctx )
            sws_freeContext(handle->img_convert_ctx);

        handle->img_convert_ctx = sws_getCachedContext(
            NULL,
            handle->video_st->codec->width, handle->video_st->codec->height,
            handle->video_st->codec->pix_fmt,
             handle->frame.width,  handle->frame.height,
            PIX_FMT_YUV420P,
            SWS_BICUBIC,
            NULL, NULL, NULL
            );

        if (handle->img_convert_ctx == NULL)
            return false;//CV_Error(0, "Cannot initialize the conversion context!");

    }

    sws_scale(
            handle->img_convert_ctx,
            handle->picture->data,
            handle->picture->linesize,
            0, handle->video_st->codec->height,
            handle->yuv_frame.data,
            handle->yuv_frame.linesize
            );

    *data =  handle->frame.data;
    *step =  handle->frame.step;
    *width =  handle->frame.width;
    *height =  handle->frame.height;
    *cn =  handle->frame.cn;

	

    return true;
}


double getProperty(struct dec_handle *handle, int property_id )
{
    if( !handle->video_st ) return 0;

    switch( property_id )
    {
    case CV_FFMPEG_CAP_PROP_POS_MSEC:
        return 1000.0*(double)handle->frame_number/get_fps(handle);
    case CV_FFMPEG_CAP_PROP_POS_FRAMES:
        return (double)handle->frame_number;
    case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
        return r2d(handle, handle->ic->streams[handle->video_stream]->time_base);
    case CV_FFMPEG_CAP_PROP_FRAME_COUNT:
        return (double)get_total_frames(handle);
    case CV_FFMPEG_CAP_PROP_FRAME_WIDTH:
        return (double)handle->frame.width;
    case CV_FFMPEG_CAP_PROP_FRAME_HEIGHT:
        return (double)handle->frame.height;
    case CV_FFMPEG_CAP_PROP_FPS:
#if LIBAVCODEC_BUILD > 4753
        //return av_q2d(handle->video_st->r_frame_rate);
		return 20;
#else
        return (double)handle->video_st->codec.frame_rate
                / (double)handle->video_st->codec.frame_rate_base;
#endif
    case CV_FFMPEG_CAP_PROP_FOURCC:
#if LIBAVFORMAT_BUILD > 4628
        return (double)handle->video_st->codec->codec_tag;
#else
        return (double)handle->video_st->codec.codec_tag;
#endif
    default:
        break;
    }

    return 0;
}

double r2d(struct dec_handle *handle, AVRational r)
{
    return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}

double get_duration_sec(struct dec_handle *handle)
{
    double sec = (double)handle->ic->duration / (double)AV_TIME_BASE;

    if (sec < handle->eps_zero)
    {
        sec = (double)handle->ic->streams[handle->video_stream]->duration * r2d(handle, handle->ic->streams[handle->video_stream]->time_base);
    }

    if (sec < handle->eps_zero)
    {
        sec = (double)handle->ic->streams[handle->video_stream]->duration * r2d(handle, handle->ic->streams[handle->video_stream]->time_base);
    }

    return sec;
}

int get_bitrate(struct dec_handle *handle)
{
    return handle->ic->bit_rate;
}

double get_fps(struct dec_handle *handle)
{
    //double fps = r2d(handle, handle->ic->streams[handle->video_stream]->r_frame_rate);
	double fps = 20;

#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0)
    if (fps < handle->eps_zero)
    {
        fps = r2d(handle, handle->ic->streams[handle->video_stream]->avg_frame_rate);
    }
#endif

    if (fps < handle->eps_zero)
    {
        fps = 1.0 / r2d(handle, handle->ic->streams[handle->video_stream]->codec->time_base);
    }

    return fps;
}

int64_t get_total_frames(struct dec_handle *handle)
{
    int64_t nbf = handle->ic->streams[handle->video_stream]->nb_frames;

    if (nbf == 0)
    {
        nbf = (int64_t)floor(get_duration_sec(handle) * get_fps(handle) + 0.5);
    }
    return nbf;
}

int64_t dts_to_frame_number(struct dec_handle *handle, int64_t dts)
{
    double sec = dts_to_sec(handle, dts);
    return (int64_t)(get_fps(handle) * sec + 0.5);
}

double dts_to_sec(struct dec_handle *handle, int64_t dts)
{
    return (double)(dts - handle->ic->streams[handle->video_stream]->start_time) *
        r2d(handle, handle->ic->streams[handle->video_stream]->time_base);
}

void seek(struct dec_handle *handle, int64_t _frame_number)
{
    _frame_number = MIN(_frame_number, get_total_frames(handle));
    int delta = 16;

    // if we have not grabbed a single frame before first seek, let's read the first frame
    // and get some valuable information during the process
    if( handle->first_frame_number < 0 && get_total_frames(handle) > 1 )
        grabFrame(handle);

    for(;;)
    {
        int64_t _frame_number_temp = MAX(_frame_number-delta, (int64_t)0);
        double sec = (double)_frame_number_temp / get_fps(handle);
        int64_t time_stamp = handle->ic->streams[handle->video_stream]->start_time;
        double  time_base  = r2d(handle, handle->ic->streams[handle->video_stream]->time_base);
        time_stamp += (int64_t)(sec / time_base + 0.5);
        if (get_total_frames(handle) > 1) av_seek_frame(handle->ic, handle->video_stream, time_stamp, AVSEEK_FLAG_BACKWARD);
        avcodec_flush_buffers(handle->ic->streams[handle->video_stream]->codec);
        if( _frame_number > 0 )
        {
            grabFrame(handle);

            if( _frame_number > 1 )
            {
                handle->frame_number = dts_to_frame_number(handle, handle->frame_pts) - handle->first_frame_number;
                //printf("_frame_number = %d, handle->frame_number = %d, delta = %d\n",
                //       (int)_frame_number, (int)handle->frame_number, delta);

                if( handle->frame_number < 0 || handle->frame_number > _frame_number-1 )
                {
                    if( _frame_number_temp == 0 || delta >= INT_MAX/4 )
                        break;
                    delta = delta < 16 ? delta*2 : delta*3/2;
                    continue;
                }
                while( handle->frame_number < _frame_number-1 )
                {
                    if(!grabFrame(handle))
                        break;
                }
                handle->frame_number++;
                break;
            }
            else
            {
                handle->frame_number = 1;
                break;
            }
        }
        else
        {
            handle->frame_number = 0;
            break;
        }
    }
}

//void seek(struct dec_handle *handle, double sec)
//{
//    seek(handle, (int64_t)(sec * get_fps(handle) + 0.5));
//}

bool setProperty(struct dec_handle *handle, int property_id, double value )
{
    if( !handle->video_st ) return false;

    switch( property_id )
    {
    case CV_FFMPEG_CAP_PROP_POS_MSEC:
    case CV_FFMPEG_CAP_PROP_POS_FRAMES:
    case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
        {
            switch( property_id )
            {
            case CV_FFMPEG_CAP_PROP_POS_FRAMES:
                seek(handle,(int64_t)value);
                break;

            case CV_FFMPEG_CAP_PROP_POS_MSEC:
                seek(handle,value/1000.0);
                break;

            case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
                seek(handle,(int64_t)(value*handle->ic->duration));
                break;
            }

            handle->frame_pts=(int64_t)value;
        }
        break;
    case CV_FFMPEG_CAP_PROP_FRAME_WIDTH:
         handle->frame.width = (int)value;
        break;
    case CV_FFMPEG_CAP_PROP_FRAME_HEIGHT:
         handle->frame.height = (int)value;
        break;
    default:
        return false;
    }

    return true;
}

////



